/**
 * ByteBuffer
 * 
 */

type Data = string | Buffer | ArrayBuffer | Buffer[];
type EncodeType = "utf8" | "ascii" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex" | undefined;

const Type_Byte = 1;
const Type_Short = 2;
const Type_UShort = 3;
const Type_Int32 = 4;
const Type_UInt32 = 5;
const Type_String = 6;//变长字符串，前两个字节表示长度
const Type_VString = 7;//定长字符串
const Type_Int64 = 8;
const Type_Float = 9;
const Type_Double = 10;
const Type_ByteArray = 11;

export default class ByteBuffer {
    private org_buf: Buffer | null = null;
    private encode: EncodeType = 'utf8';
    private offset = 0;
    private list: any[] = [];
    private endian = 'B';

    constructor(org_buf?: Buffer, offset: number = 0) {
        org_buf && (this.org_buf = org_buf);
        this.offset = offset;
    }

    reset(offset = 0) {
        this.offset = offset;
        this.list = [];
    }

    from(buff: Buffer | null, offset = 0) {
        if (buff == null) {
            return this;
        }
        this.org_buf = buff;
        this.offset = offset;
        return this;
    }

    append(buffer: ByteBuffer) {
        if (this.list.length > 0) {
            this.toBuffer();
        }
        this.org_buf = Buffer.concat([this.org_buf!, buffer.buffer!]);
        this.offset = this.offset + buffer.offset;
        return this;
    }

    get length() {
        return this.org_buf == null ? 0 : this.org_buf.length;
    }

    get buffer() {
        return this.org_buf;
    }

    encoding(encode: EncodeType) {
        this.encode = encode;
        return this;
    };

    bigEndian() {
        this.endian = 'B';
        return this;
    };


    //指定字节序 为LittleEndian
    littleEndian() {
        this.endian = 'L';
        return this;
    };

    byte(val?: string, index?: number) {
        if (val == undefined || val == null) {
            if (this.org_buf == null) {
                return this;
            }
            let t = this.org_buf.readUInt8(this.offset);
            this.offset += 1;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_Byte, d: val, l: 1 });
            this.offset += 1;
        }
        return this;
    };

    short(val: number, index: number): this;
    short(val: number, index?: number): this;
    short(val?: number, index?: number): number;
    short(val?: number, index?: number) {
        if (val == undefined || val == null) {
            let t: number = (this.org_buf as any)['readInt16' + this.endian + 'E'](this.offset);
            this.offset += 2;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_Short, d: val, l: 2 });
            this.offset += 2;
        }
        return this;
    };

    ushort(val: number, index: number): this;
    ushort(val: number, index?: number): this;
    ushort(val?: number, index?: number): number;
    ushort(val?: number, index?: number) {
        if (val == undefined || val == null) {
            let t = (this.org_buf as any)['readUInt16' + this.endian + 'E'](this.offset);
            this.offset += 2;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_UShort, d: val, l: 2 });
            this.offset += 2;
        }
        return this;
    };

    int32(val: number, index: number): this;
    int32(val: number, index?: number): this;
    int32(val?: number, index?: number): number;
    int32(val?: number, index?: number): this | number {
        if (val == undefined || val == undefined) {
            let t = (this.org_buf as any)['readInt32' + this.endian + 'E'](this.offset);
            this.offset += 4;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_Int32, d: val, l: 4 });
            this.offset += 4;
        }
        return this;
    };

    uint32(val: number, index: number): this;
    uint32(val: number, index?: number): this;
    uint32(val?: number, index?: number): number;
    uint32(val?: number, index?: number) {
        if (val == undefined || val == null) {
            let t = (this.org_buf as any)['readUInt32' + this.endian + 'E'](this.offset);
            this.offset += 4;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_UInt32, d: val, l: 4 });
            this.offset += 4;
        }
        return this;
    };

    /**
    * 变长字符串 前2个字节表示字符串长度
    **/
    string(val: string, index: number): this;
    string(val: string, index?: number): this;
    string(val?: string, index?: number): string;
    string(val?: string, index?: number) {
        if (val == undefined || val == null) {
            let len = (this.org_buf as any)['readInt16' + this.endian + 'E'](this.offset);
            this.offset += 2;
            let t = (this.org_buf as any).toString(this.encode, this.offset, this.offset + len)
            this.offset += len;
            return t;
        } else {
            let len: number = 0;
            if (val) {
                len = Buffer.byteLength(val, this.encode);
            }
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_String, d: val, l: len });
            this.offset += len + 2;
        }
        return this;
    };

    /**
    * 定长字符串 val为null时，读取定长字符串（需指定长度len）
    **/
    vstring(val: string, len: number, index: number): this;
    vstring(val: string, len: number, index?: number): this;
    vstring(val: string, len?: number, index?: number): this;
    vstring(val?: string, len?: number, index?: number): string;
    vstring(val?: string, len?: number, index?: number) {
        if (!len) {
            throw new Error('vstring must got len argument');
            return this;
        }
        if (val == undefined || val == null) {
            var vlen = 0;//实际长度
            for (var i = this.offset; i < this.offset + len; i++) {
                if ((this.org_buf as any)[i] > 0)
                    vlen++;
            }
            let t = (this.org_buf as any).toString(this.encode, this.offset, this.offset + vlen);
            this.offset += len;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_VString, d: val, l: len });
            this.offset += len;
        }
        return this;
    };

    int64(val: number, index: number): this;
    int64(val: number, index?: number): this;
    int64(val?: number, index?: number): number;
    int64(val?: number, index?: number) {
        if (val == undefined || val == null) {
            let t = (this.org_buf as any)['readDouble' + this.endian + 'E'](this.offset);
            this.offset += 8;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_Int64, d: val, l: 8 });
            this.offset += 8;
        }
        return this;
    };

    float(val: number, index: number): this;
    float(val: number, index?: number): this;
    float(val?: number, index?: number): number;
    float(val?: number, index?: number) {
        if (val == undefined || val == null) {
            let t = (this.org_buf as any)['readFloat' + this.endian + 'E'](this.offset);
            this.offset += 4;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_Float, d: val, l: 4 });
            this.offset += 4;
        }
        return this;
    };

    double(val: number, index: number): this;
    double(val: number, index?: number): this;
    double(val?: number, index?: number): number;
    double(val?: number, index?: number) {
        if (val == undefined || val == null) {
            let t = (this.org_buf as any)['readDouble' + this.endian + 'E'](this.offset);
            this.offset += 8;
            return t;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_Double, d: val, l: 8 });
            this.offset += 8;
        }
        return this;
    };

    /**
    * 写入或读取一段字节数组
    **/
    byteArray(val?: string, len?: number, index?: number) {
        if (!len) {
            throw new Error('byteArray must got len argument');
            return this;
        }
        if (val == undefined || val == null) {
            var arr = [];
            for (var i = this.offset; i < this.offset + len; i++) {
                if (i < (this.org_buf as Buffer).length) {
                    arr.push((this.org_buf as Buffer).readUInt8(i));
                } else {
                    arr.push(0);
                }
            }
            this.offset += len;
            return arr;
        } else {
            this.list.splice(index != undefined ? index : this.list.length, 0, { t: Type_ByteArray, d: val, l: len });
            this.offset += len;
        }
        return this;
    };

    /**
    * 打包成二进制
    **/
    toBuffer() {
        this.org_buf = Buffer.alloc(this.offset);
        var offset = 0;
        for (var i = 0; i < this.list.length; i++) {
            switch (this.list[i].t) {
                case Type_Byte:
                    this.org_buf.writeUInt8(this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_Short:
                    (this.org_buf as any)['writeInt16' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_UShort:
                    (this.org_buf as any)['writeUInt16' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_Int32:
                    (this.org_buf as any)['writeInt32' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_UInt32:
                    (this.org_buf as any)['writeUInt32' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_String:
                    //前2个字节表示字符串长度
                    (this.org_buf as any)['writeInt16' + this.endian + 'E'](this.list[i].l, offset);
                    offset += 2;
                    this.org_buf.write(this.list[i].d, offset, this.encode);
                    offset += this.list[i].l;
                    break;
                case Type_VString:
                    var vlen = Buffer.byteLength(this.list[i].d, this.encode);//字符串实际长度
                    this.org_buf.write(this.list[i].d, offset, this.encode);
                    //补齐\0
                    for (var j = offset + vlen; j < offset + this.list[i].l; j++) {
                        this.org_buf.writeUInt8(0, j);
                    }
                    offset += this.list[i].l;
                    break;
                case Type_Int64:
                    (this.org_buf as any)['writeDouble' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_Float:
                    (this.org_buf as any)['writeFloat' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_Double:
                    (this.org_buf as any)['writeDouble' + this.endian + 'E'](this.list[i].d, offset);
                    offset += this.list[i].l;
                    break;
                case Type_ByteArray:
                    var indx = 0;
                    for (var j = offset; j < offset + this.list[i].l; j++) {
                        if (indx < this.list[i].d.length) {
                            this.org_buf.writeUInt8(this.list[i].d[indx], j);
                        } else {//不够的话，后面补齐0x00
                            this.org_buf.writeUInt8(0, j);
                        }
                        indx++
                    }
                    offset += this.list[i].l;
                    break;
            }
        }
        return this.org_buf;
    };

    /**
    * 未读数据长度
    **/
    getAvailable() {
        if (!this.org_buf) {
            return this.offset;
        }
        return this.org_buf.length - this.offset;
    };
}
